/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019.
 * Description: tty status trace function
 * Author: nixiaoming
 * Create: 2018-3-4
 */

#include <linux/time.h>
#include <linux/rtc.h>
#include <linux/tty.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/mutex.h>
#include <trace/rtos_tracepoint.h>

#include <linux/sysfs.h>

#include <linux/module.h>
#include <linux/rtos_time.h>

MODULE_LICENSE("Dual BSD/GPL");

/*
 * enable: 1 // register tracepoint
 * enable: 0 // unregister tracepoint
 */
static int enable;

static DEFINE_MUTEX(tty_status_switch_lock);

struct kobject *tty_status_trace_kobj;

/* tracepointer handler of tty_status, print current task context */
static void tty_status_change_handler(void *ignore, struct tty_struct *tty,
				      unsigned long stopped, void *func)
{
	if (printk_ratelimit()) {
		rtos_print_add_timestamp("TSTP");
		pr_warn("tty name: %s; tty->stopped value: %lu;function name:(%ps)\n",
			tty_name(tty), stopped, func);
		dump_stack();
	}
}

static ssize_t enable_show(struct kobject *kobj,
			   struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "enable: %d\n", enable);
}

static ssize_t enable_store(struct kobject *kobj,
			    struct kobj_attribute *attr,
			    const char *buf, size_t count)
{
	int ret = count;
	int ret_reg;

	if (count > 2 || (count == 2 && buf[1] != '\n')) {
		pr_warn("enable only accept '1' or '0' as input, no more than one character\n");
		return -EINVAL;
	}

	if (mutex_lock_interruptible(&tty_status_switch_lock))
		return -EINTR;

	/* read first char */
	switch (buf[0]) {
	case '1':
		if (enable == 1)
			break;

		ret_reg = register_trace_tty_status(tty_status_change_handler, NULL);
		if (ret_reg && ret_reg != -EEXIST) {
			pr_warn("wakeup trace: Couldn't activate tracepoint"
				"probe to tty_status. error code: %d\n", ret_reg);
			ret = ret_reg;
			break;
		}

		pr_warn("activate tracepoint tty_status successfully.\n");
		enable = 1;

		break;
	case '0':
		if (enable == 0)
			break;

		unregister_trace_tty_status(tty_status_change_handler, NULL);
		tracepoint_synchronize_unregister();
		pr_warn("deactivate tracepoint tty_status successfully.\n");
		enable = 0;

		break;
	default:
		pr_warn("enable only accept '1' or '0' as input, actual chr: '%c'\n", buf[0]);
		ret = -EINVAL;
	}

	mutex_unlock(&tty_status_switch_lock);
	return ret;
}

static struct kobj_attribute tty_status_trace_enable_attr = __ATTR(enable, 0640, enable_show, enable_store);

static struct attribute *attrs[] = {
	&tty_status_trace_enable_attr.attr,
	NULL,   /* need to NULL terminate the list of attributes */
};

static struct attribute_group attr_group = {
	.attrs = attrs,
};

static int __init trace_tty_status_init(void)
{
	int ret;

	tty_status_trace_kobj = kobject_create_and_add("tty_status_trace", kernel_kobj);
	if (!tty_status_trace_kobj) {
		pr_warn("create tty_status_trace in /sys/kernel/ failed");
		return -EEXIST;
	}

	ret = sysfs_create_group(tty_status_trace_kobj, &attr_group);
	if (ret != 0) {
		pr_warn("create attribute enable failed\n");
		kobject_put(tty_status_trace_kobj);
		return ret;
	}

	pr_warn("create /sys/kernel/tty_status_trace succ.\n");
	return 0;
}

static void __exit trace_tty_status_exit(void)
{
	sysfs_remove_group(tty_status_trace_kobj, &attr_group);
	kobject_put(tty_status_trace_kobj);
	if (enable == 1) {
		unregister_trace_tty_status(tty_status_change_handler, NULL);
		tracepoint_synchronize_unregister();
	}
	pr_warn("remove /sys/kernel/tty_status_trace.\n");
}

module_init(trace_tty_status_init);
module_exit(trace_tty_status_exit);
